[Feature][C++] Support mmap as an optional local file read backend - #920
[Feature][C++] Support mmap as an optional local file read backend#920Young-Leo wants to merge 1 commit into
Conversation
Add AUTO, MMAP, and PREAD configuration across the C++, C, and Python APIs. Implement Windows and POSIX mappings with fallback and explicit errors, plus lifecycle tests, documentation, and comparison benchmarks for issue apache#903.
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Adds configurable local-file read backends (AUTO/MMAP/PREAD) with mmap/file-mapping support across C++, C, and Python APIs, plus tests and benchmarking/docs to validate behavior and performance characteristics.
Changes:
- Introduces
FileReadBackendselection (process-wide) and wires it intoReadFileopen/read paths with AUTO fallback vs required-MMAP errors. - Exposes backend configuration via C wrapper + Python bindings (config dict + dedicated get/set functions) and adds a new mapping error code.
- Adds C++/Python tests, updates CLIs/docs, and adds an optional CMake benchmark target.
Reviewed changes
Copilot reviewed 29 out of 29 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| python/tsfile/tsfile_py_cpp.pyx | Exposes backend get/set APIs and includes backend in config dict. |
| python/tsfile/tsfile_py_cpp.pxd | Exports new Python C-API symbols for backend get/set. |
| python/tsfile/tsfile_cpp.pxd | Declares C wrapper enum + functions for backend configuration. |
| python/tsfile/exceptions.py | Adds FILE_MAP_ERROR and maps it to FileMapError. |
| python/tsfile/constants.py | Introduces FileReadBackend IntEnum. |
| python/tsfile/init.py | Re-exports backend configuration helpers. |
| python/tests/test_write_and_read.py | Adds Python configuration round-trip + equivalence test for PREAD/MMAP. |
| python/tests/test_exceptions.py | Adds test coverage for the new FileMapError mapping. |
| python/README.md | Documents Python backend configuration and behavior caveats. |
| python/README-zh.md | Chinese docs for Python backend configuration and caveats. |
| cpp/tools/format/output_format.cc | Adds CLI-readable message for E_FILE_MAP_ERR. |
| cpp/test/tools/output_format_test.cc | Tests new error-message mapping for E_FILE_MAP_ERR. |
| cpp/test/file/read_file_test.cc | Adds lifecycle/behavior tests for AUTO/MMAP/PREAD and fallback/errors. |
| cpp/test/cwrapper/cwrapper_test.cc | Adds C API backend config round-trip/validation test. |
| cpp/src/utils/injection.h | Exposes test-only injection enable/disable API. |
| cpp/src/utils/errno_define.h | Adds E_FILE_MAP_ERR error code. |
| cpp/src/file/read_file.h | Extends ReadFile with backend selection + mapping state. |
| cpp/src/file/read_file.cc | Implements mapping/unmapping + backend-aware read/generation logic. |
| cpp/src/cwrapper/tsfile_cwrapper.h | Documents/exposes C API for backend selection. |
| cpp/src/cwrapper/tsfile_cwrapper.cc | Implements C API backend config functions. |
| cpp/src/cwrapper/errno_define_c.h | Exposes RET_FILE_MAP_ERR for C API clients. |
| cpp/src/common/global.h | Declares global backend getter/setter (outside ConfigValue for ABI). |
| cpp/src/common/global.cc | Implements atomic global backend config + test injection helpers. |
| cpp/src/common/config/config.h | Defines common::FileReadBackend enum. |
| cpp/bench_mark/bench_mark_src/read_backend_benchmark.cc | Adds benchmark tool to compare MMAP vs PREAD workloads. |
| cpp/bench_mark/README.md | Documents benchmark build/run protocol and interpretation. |
| cpp/README.md | Documents backend selection behavior + return codes in C/C++. |
| cpp/README-zh.md | Chinese docs for backend selection behavior + return codes. |
| cpp/CMakeLists.txt | Adds BUILD_BENCHMARK option and benchmark target wiring. |
Suppressed comments (1)
python/tsfile/exceptions.py:1
get_exception(...)(per the provided context) formats the exception message viaERROR_MESSAGES.get(code, "Unknown library error")and passes it ascontext. This diff addsFileMapErrortoERROR_MAPPING, but does not add a corresponding entry toERROR_MESSAGES, which likely means clients will see"Unknown library error"for code 55 instead of the intended “Failed to memory-map file”. Add the missingERROR_MESSAGES[55]entry (and consider asserting the message inpython/tests/test_exceptions.pyso this doesn’t regress).
# Licensed to the Apache Software Foundation (ASF) under one
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| cpdef void set_tsfile_config(dict new_config): | ||
| if "file_read_backend_" in new_config: | ||
| set_file_read_backend(new_config["file_read_backend_"]) | ||
| if "tsblock_mem_inc_step_size_" in new_config: | ||
| _check_uint32(new_config["tsblock_mem_inc_step_size_"]) |
| ret = map_ret; | ||
| } else { | ||
| LOGW("mmap unavailable for " << file_path_.c_str() | ||
| << "; falling back to pread"); |
Summary
AUTO,MMAP, andPREADlocal file read backends for the C++ reader.mmapand Windows file mapping.Backend behavior
AUTOprefers memory mapping for supported regular files and falls back to the positioned-read backend when mapping is unavailable.MMAPrequires memory mapping and returns an explicit error if the file cannot be mapped.PREADpreserves the existing positioned-read behavior.Benchmark
These are preliminary Windows Release results. They are not used as a performance threshold because relative performance depends on the access pattern, cache state, filesystem, storage device, and hardware.
Environment and protocol
-O3ReadFile::read()scansCache-state study
The low-level benchmark separates three different cache and mapping states:
The last two rows differ only in whether the same MMAP view was completely traversed before timing.
With a newly created mapping, the first sequential traversal incurred approximately 17,233 page faults and reached only 0.43x the throughput of PREAD. After pre-touching the same mapping, these page faults disappeared and MMAP throughput increased from approximately 2.54 GiB/s to 17.03 GiB/s, reaching 2.99x the PREAD throughput.
This explains why MMAP may be slower for the first complete sequential scan even when the OS file cache is already warm: the process still needs to establish page-table mappings when it touches the MMAP pages for the first time.
The approximate cold-cache state used a unique
CopyFileExW(..., COPY_FILE_NO_BUFFERING)copy for each round instead of clearing the machine-wide standby list. Copy preparation time was excluded from the measured throughput. Storage-controller caches, filesystem metadata caches, and background system activity may still affect this result.Windows
PageFaultCountis a process-wide total and does not directly distinguish soft and hard page faults. In the warm-file-cache experiment, the MMAP page faults are inferred to be primarily soft faults because every file was completely scanned before opening the measured backend.The current MMAP implementation still copies mapped bytes into the caller-provided buffer in
ReadFile::read(), so this is not a zero-copy benchmark.TsFileDataFrame end-to-end benchmark
The end-to-end benchmark represents a training-style workload:
For this training-style workload, MMAP improved end-to-end throughput by:
The relative improvement becomes smaller with four workers because decompression, decoding, NumPy materialization, scheduling, and parallel-query overhead account for a larger part of the total runtime.
Unlike the low-level sequential scan, the DataFrame reader performs many fine-grained metadata, chunk, and page reads. MMAP avoids repeated positioned-read calls in this access pattern, so it still improves the first DataFrame epoch despite the initial page-touch overhead.
Native stack-sampling observations
Periodic GDB native stack sampling was collected for both backends:
ReadFile/NtReadFilein 94–99% of captured stacks.ReadFile/NtReadFilestacks and were dominated by the memory-copy path.ReadFile/NtReadFileappeared in 8.5–9.9% of PREAD stacks and in none of the MMAP stacks.These results confirm that the MMAP backend removes the repeated positioned-read path, while decompression and decoding remain substantial end-to-end costs.
The profiles were collected through periodic GDB interruption rather than ETW hardware sampling. Stack percentages describe the presence of a function in captured stacks and should not be interpreted as direct elapsed-time percentages.
Interpretation
MMAP is not faster for every workload.
A newly created mapping can be slower for a one-time sequential scan because the first traversal must establish the process page-table mappings. MMAP is more beneficial when:
PREAD can remain preferable for one-time sequential scans. Users can explicitly select
PREADfor that access pattern.AUTOis a compatibility-oriented fallback policy rather than a workload-adaptive performance policy: it prefers MMAP for supported regular files and falls back to PREAD only when mapping is unavailable.Validation
Closes #903